/*
 * Copyright (C) 2010 Yury Kudryashov.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package name.kudryashov.playground;

import name.kudryashov.base.Argument;
import name.kudryashov.collections.Tuple2;
import name.kudryashov.functions.Predicate2;

import java.util.HashMap;
import java.util.Map;

import static name.kudryashov.collections.Tuple.tuple;

public class StateMachine<T extends Enum> {
    private final Class<T> clazz;
    // Map of from and to states to the corresponding functions
    private final Map<Tuple2<T, T>, Tuple2<Predicate2<T, T>, Predicate2<T, T>>> transitionRules = new HashMap<Tuple2<T,T>, Tuple2<Predicate2<T,T>, Predicate2<T,T>>>();
    private T state;

    public StateMachine(Class<T> clazz, T initialState) {
        this.clazz = clazz;
        state = initialState;
    }

    public StateMachine<T> transition(T from, T to, Predicate2<T, T> beforeTransition, Predicate2<T, T> afterTransition) {
        Argument.requireNotNull(from, "from");
        Argument.requireNotNull(to, "to");

        transitionRules.put(tuple(from, to), tuple(beforeTransition, afterTransition));
        return this;
    }

    public StateMachine<T> illegal(T from, T to) {
        return transition(from, to, new Predicate2<T, T>() {
            public boolean apply(T arg1, T arg2) {
                throw new IllegalTransitionException();
            }
        }, null);
    }

    public StateMachine<T> blocked(T from, T to) {
        return transition(from, to, new Predicate2<T, T>() {
            public boolean apply(T arg1, T arg2) {
                return false;
            }
        }, null);
    }

    public T getState() {
        return state;
    }

    public boolean setState(T newState) {
        Argument.requireNotNull(newState, "newState");
        Tuple2<Predicate2<T, T>, Predicate2<T, T>> fs = transitionRules.get(tuple(state, newState));
        if(fs == null) {
            state = newState;
            return true;
        }

        Predicate2<T, T> allowF = fs.get1();
        Predicate2<T, T> afterF = fs.get2();
        boolean transitionAllowed = allowF == null || allowF.apply(state, newState);
        boolean transitionConfirmed = false;
        if(transitionAllowed) {
            T oldState = state;
            state = newState;
            transitionConfirmed = afterF == null || afterF.apply(oldState, newState);
            if(!transitionConfirmed) {
                state = oldState;
            }
        }
        return transitionAllowed && transitionConfirmed;
    }

    
}
